﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Resources;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace CrmRulo
{
    /// <summary>
    /// Parses a RDL file looking for localizable items and creates output reports for each language found
    /// in resources files in a directory.
    /// </summary>
    public class ReportsLocalizer
    {
        private readonly string _inputDirectory;
        private readonly string _outputDirectory;
        private readonly string _defaultLanguage;

        private static readonly XNamespace Xmlns = "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition";

        public ReportsLocalizer(string inputDirectory, string outputDirectory, string defaultLanguage)
        {
            _inputDirectory = inputDirectory;
            _outputDirectory = outputDirectory;
            _defaultLanguage = defaultLanguage;
        }

        /// <summary>
        /// Exeute resgen to compile a .resx file to a .resources file
        /// </summary>
        /// <param name="reportFileName">The full path to the report</param>
        /// <param name="language">The language to lozalize the report to</param>
        public void CompileResourceFileForReport(string reportFileName, string language)
        {
            var reportNameNoExt = Path.GetFileNameWithoutExtension(reportFileName);
            string resourceFile;
            if (language == _defaultLanguage)
                resourceFile = String.Format("{0}\\{1}.resx", _inputDirectory, reportNameNoExt);
            else
                resourceFile = String.Format("{0}\\{1}.{2}.resx", _inputDirectory, reportNameNoExt, language);

            if (!File.Exists(resourceFile))
            {
                Console.WriteLine(resourceFile + @" not found. Defaulting to reports.{0}.resx", language);
                return;
            }

            string outputFileName;
            if (language == _defaultLanguage)
                outputFileName = String.Format("{0}\\{1}.resources", _inputDirectory, reportNameNoExt);
            else
                outputFileName = String.Format("{0}\\{1}.{2}.resources", _inputDirectory, reportNameNoExt, language);


            if (File.Exists(outputFileName))
            {
                Console.WriteLine(outputFileName + @" already compiled. Skipping.");
                return;
            }

            string parameters = String.Format("/useSourcePath \"{0}\"", resourceFile);
            Console.WriteLine(@"Executing resgen.exe " + parameters);
            var returnCode = CommandLineHelper.Run("resgen.exe", parameters);
            Console.WriteLine(@"ResGen output:" + Environment.NewLine + returnCode);
        }

        /// <summary>
        /// Localize a report to a given language
        /// </summary>
        /// <param name="pathToReport">The full path to the record file</param>
        /// <param name="language">The language code string to localize to</param>
        public void Localize(string pathToReport, string language)
        {
            var cultureInfo = CultureInfo.GetCultureInfo(language);
            var reportDirectory = Path.GetDirectoryName(pathToReport);
            var reportNameNoExt = Path.GetFileNameWithoutExtension(pathToReport);

            string resourcesFileName;
            if (language == _defaultLanguage)
                resourcesFileName = String.Format("{0}\\{1}.resources", reportDirectory, reportNameNoExt);
            else
                resourcesFileName = String.Format("{0}\\{1}.{2}.resources", reportDirectory, reportNameNoExt, language);

            ResourceManager rm;

            if (File.Exists(resourcesFileName))
                rm = ResourceManager.CreateFileBasedResourceManager(reportNameNoExt, reportDirectory, null);
            else
            {
                if (language != _defaultLanguage)
                    resourcesFileName = String.Format("{0}\\reports.{1}.resources", reportDirectory, language);
                else
                    resourcesFileName = String.Format("{0}\\reports.resources", reportDirectory);
                if (File.Exists(resourcesFileName))
                    rm = ResourceManager.CreateFileBasedResourceManager("reports", reportDirectory, null);
                else
                {
                    var msg = String.Format("Compiled resources file '{0}' not found.", resourcesFileName);
                    throw new Exception(msg);
                }
            }

            var reportText = File.ReadAllText(pathToReport);

            var result = new StringBuilder(reportText);

            var matches = FindAllLocalizableLabelsInReport(reportText);
            foreach (var match in matches)
            {
                var key = match.Substring(1, match.Length - 2);
                var replacement = rm.GetString(key, cultureInfo);
                Console.WriteLine(@"{0} -> {1}", match, replacement);
                result.Replace(match, replacement);
            }
            var outputReport = FixSubreportDependencies(result.ToString(), language);
            var newFilePath = String.Format(@"{0}\{1}-{2}.rdl", _outputDirectory, reportNameNoExt, language);
            File.WriteAllText(newFilePath, outputReport);
        }

        /// <summary>
        /// Search all the occurrences of localizable labels in the report text.
        /// Localizable labels are enclosed with the dollar ($) sign.
        /// </summary>
        /// <param name="text">Text contents of the report file</param>
        /// <returns>Array with the labels found, including the enclosing dollar signs</returns>
        public IEnumerable<string> FindAllLocalizableLabelsInReport(string text)
        {
            var re = new Regex("\\$[a-zA-Z0-9]+\\$");
            var matches = re.Matches(text);
            var values = matches.Cast<Match>().Select(m => m.Value);
            return values;
        }

        /// <summary>
        /// Fix all the occurrences of subreports to adapt to the new localized report name
        /// </summary>
        /// <param name="reportText">Text contents of the report file</param>
        /// <param name="cultureName">Language to localize in</param>
        /// <returns>The modified report text</returns>
        public string FixSubreportDependencies(string reportText, string cultureName)
        {
            var report = XDocument.Parse(reportText);
            var subreportReferences = report.Descendants(Xmlns + "Subreport");
            foreach (var subreportNode in subreportReferences)
            {
                var nameNode = subreportNode.Element(Xmlns + "ReportName");
                Util.Assert(nameNode != null, "ReportName element not found in subreport");
                if (nameNode != null) nameNode.Value += "-" + cultureName;
            }
            return report.ToString(SaveOptions.DisableFormatting);
        }


        /// <summary>
        /// Scan the directory where a report is located looking for resource files
        /// with .resx suffix. Fallback option is reports.resx file
        /// </summary>
        /// <param name="pathToReport">Full path to the report file</param>
        /// <param name="throwExceptionIfNotFound"></param>
        /// <returns>An array of strings with the language string codes</returns>
        public IEnumerable<string> FindAvailableLanguagesForReport(string pathToReport, bool throwExceptionIfNotFound)
        {
            var languages = new List<string>(new[] { _defaultLanguage });
            var reportNoExt = Path.GetFileNameWithoutExtension(pathToReport);
            var directoryPath = Path.GetDirectoryName(pathToReport);
            var directoryInfo = new DirectoryInfo(directoryPath);
            var resourceFiles = directoryInfo.GetFiles(reportNoExt + "*.resx");
            if (resourceFiles.Length == 0 && reportNoExt != "reports")
            {
                Console.WriteLine("No resource files were found for report '{0}'. Trying 'reports.XX.resx'", reportNoExt);
                resourceFiles = directoryInfo.GetFiles("reports*.resx");
            }

            if (resourceFiles.Length == 0)
            {
                if (throwExceptionIfNotFound)
                    throw new Exception("No resource files were found.");
                return new string[] { };
            }

            foreach (var resourceFile in resourceFiles)
            {
                var fileNameNoExt = Path.GetFileNameWithoutExtension(resourceFile.FullName);
                if (fileNameNoExt != null && fileNameNoExt.Contains("."))
                {
                    var parts = fileNameNoExt.Split('.');
                    var language = parts[1];
                    if (!languages.Contains(language))
                        languages.Add(language);
                }
            }
            return languages;
        }

    }
}
